package tasks;

import exceptions.ChangeHostTypeException;
import exceptions.CreateInterfaceException;
import exceptions.TaskException;
import graph.TypeResolver;
import graph.adapters.IAdapter;
import graph.adapters.IJavaElementToIPackageFragmentCastAdapter;
import graph.filters.IFilter;
import graph.filters.IJavaElementElementTypeFilter;
import graph.searchers.IJavaElementTreeClaimer;
import graph.searchers.Searcher;
import graph.visitors.TypeDeclarationSearcher;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.UndoEdit;
import tasks.ITask;

public class CreateInterfaceTask implements ITask {
	
	private boolean _isActive = true;
	
	private String _interfaceName = null;
	
	private List<MethodInvocation> _methods = null;
	
	private CompilationUnit _compilationUnit = null;
	
	private ICompilationUnit _iCompilationUnit = null;
	
	private IDocument _document = null;

	private Error _lastError = null;
	
	private static final String _taskName = "Create Interface";	
	
	public CreateInterfaceTask(List<MethodInvocation> groupedInvocations) {
		_methods = groupedInvocations;
		ASTNode inClassMethod = _methods.get(0); 
		while ( inClassMethod.getNodeType() != ASTNode.METHOD_DECLARATION)
			inClassMethod = inClassMethod.getParent();
		_interfaceName  = "I"+_methods.get(0).resolveMethodBinding().getDeclaringClass().getName()+"__"+((MethodDeclaration)inClassMethod).getName().getFullyQualifiedName();
		
		_document = new Document("");
		
		ASTParser parser = ASTParser.newParser(AST.JLS3);
		parser.setSource(_document.get().toCharArray());
		
		_compilationUnit = (CompilationUnit) parser.createAST(null);
	}

	private IPackageFragment getParentNode(IJavaElement child, int elementType ) {
		Searcher<Integer, IPackageFragment, IJavaElement> s = new Searcher<Integer, IPackageFragment, IJavaElement>();
		Iterator<IJavaElement> treeClaimer = new IJavaElementTreeClaimer(child);
		IFilter<Integer, IJavaElement> elementTypeFilter = new IJavaElementElementTypeFilter();
		elementTypeFilter.setEtalon(elementType);
		IAdapter<IPackageFragment, IJavaElement> caster = new IJavaElementToIPackageFragmentCastAdapter();
		List<IPackageFragment> searchResults = s.search(treeClaimer, elementTypeFilter, caster);
		if (searchResults.size()==1)
			return searchResults.get(0);
		//else
		//	throw new Exception("Wrong number of found nodes");
		//TODO:Exceptions
		return null;
	}
	
	/** To perform CreateInterfaceForMethodInvocationTask means to
	 * create interface for all MethodInvocations called on the
	 * exemplar. 
	 * @throws TaskException */
	@Override
	public void perform(Map<String,String> params) throws TaskException {
		String error = null;
		IPackageFragment packageFragment = getParentNode(_methods.get(0).resolveMethodBinding().getJavaElement(),
				IJavaElement.PACKAGE_FRAGMENT);
		System.out.println("CreateInterfaceForMethodInvocationTask>Checked out IPackageFragment:"+packageFragment.getElementName());			
		try {
			//TODO:check if interface name is already occupied
			if (((IPackageFragment)packageFragment).getKind() == IPackageFragmentRoot.K_SOURCE)
				createInterface((IPackageFragment)packageFragment, params.get("interfaceName"));
		} catch (Exception e) {
			error = "CreateInterfaceTask>Can't create interface: "+e.getMessage();
			throw new CreateInterfaceException(error);
		}
		_isActive = false;
	}
	
	@Override
	public String toString() {
		String taskDescription = "Create interface including methods:";
		for (MethodInvocation invocation: _methods)
			taskDescription += " "+invocation.resolveMethodBinding().getName()+";";
		taskDescription += ".";
		return taskDescription;
	}
	
	private void createInterface(IPackageFragment mypackage, String interfaceName) throws JavaModelException {
		System.out.println("CreateInterfaceForMethodInvocationTask>CreateInterface starts");
		
		
		AST ast = _compilationUnit.getAST();
		ASTRewrite rewriter = ASTRewrite.create(ast);
			
		PackageDeclaration pD = ast.newPackageDeclaration();
		System.out.println("CreateInterfaceForMethodInvocationTask>mypackage.getElementName():"+mypackage.getElementName());
		pD.setName(ast.newSimpleName(mypackage.getElementName()));
		ListRewrite lRW = rewriter.getListRewrite(_compilationUnit, CompilationUnit.TYPES_PROPERTY);
		lRW.insertLast(pD, null);
		
		TypeDeclaration tD = ast.newTypeDeclaration();
		System.out.println("CreateInterfaceForMethodInvocationTask>interfaceName:"+interfaceName);
		tD.setName(ast.newSimpleName(interfaceName));
		tD.setInterface(true);
		ast = tD.getAST();
		
		for (MethodInvocation invocation: _methods) {
			IMethodBinding methodBinding = invocation.resolveMethodBinding();
			MethodDeclaration newMethodDeclaration = ast.newMethodDeclaration();
			newMethodDeclaration.setName(ast.newSimpleName(methodBinding.getName()));
			System.out.println("CreateInterfaceForMethodInvocationTask>Method name:"+invocation.getName().getFullyQualifiedName());
			Type returnType = TypeResolver.getType(ast, methodBinding.getReturnType());
			newMethodDeclaration.setReturnType2(returnType);
			IMethod m = (IMethod) methodBinding.getMethodDeclaration().getJavaElement();
			String[] paramNames = m.getParameterNames();
			ITypeBinding[] params= methodBinding.getParameterTypes();
			for (int i=0; i<params.length; i++) {
				SingleVariableDeclaration var= ast.newSingleVariableDeclaration();
				System.out.println("CreateInterfaceForMethodInvocationTask>"+i+" parameter: "+params[i].getName());
				var.setType(TypeResolver.getType(ast, params[i]));
				//var.setVarargs(true);
				System.out.println("CreateInterfaceForMethodInvocationTask>"+i+"parameter name: "+paramNames[i]);
				var.setName(ast.newSimpleName(paramNames[i]));
				newMethodDeclaration.parameters().add(var);
			}
			tD.bodyDeclarations().add(newMethodDeclaration);
			//TODO:exceptions
		}
		lRW.insertLast(tD, null);
		
		/** Here comes creation of java file based on a new TypeDeclaration*/
		TextEdit edits = rewriter.rewriteAST(_document, null);
		try {
		    edits.apply(_document);
		} catch(MalformedTreeException e) {
		    e.printStackTrace();
		} catch(BadLocationException e) {
		    e.printStackTrace();
		}
		_iCompilationUnit = mypackage.createCompilationUnit(interfaceName+".java", _document.get(), true, null);
		
		ITypeBinding resolveTypeBinding = _methods.get(0).getExpression().resolveTypeBinding();
		
		IJavaElement iCompilationUnitOfSourceClass = resolveTypeBinding.getJavaElement(); 
		while (iCompilationUnitOfSourceClass.getElementType() != IJavaElement.COMPILATION_UNIT)
			iCompilationUnitOfSourceClass = iCompilationUnitOfSourceClass.getParent();
		
		CompilationUnit compilationUnitOfSourceClass = parse((ICompilationUnit)iCompilationUnitOfSourceClass);
		TypeDeclarationSearcher tDS = new TypeDeclarationSearcher(resolveTypeBinding.getName());
		compilationUnitOfSourceClass.accept(tDS);
		
		try {
			ResourcesPlugin.getWorkspace().save(true, null);
		} catch (CoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		/*try {
			JavaUI.openInEditor(iCompilationUnitOfSourceClass,true,true);
		} catch (PartInitException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}*/
		System.out.println("CreateInterfaceForMethodInvocationTask>CreateInterface ends.");
	}
	
	protected CompilationUnit parse(ICompilationUnit unit) {
		ASTParser parser = ASTParser.newParser(AST.JLS3); 
		parser.setKind(ASTParser.K_COMPILATION_UNIT);
		parser.setSource(unit);
		parser.setEnvironment(null, null, null, true);
		parser.setResolveBindings(true); // we need bindings later on
		return (CompilationUnit) parser.createAST(null /* IProgressMonitor */); // parse
	}

	@Override
	public void undo() {
		System.out.println("UNDO CIT");
		//_compilationUnit.delete();
		try {
			if (_iCompilationUnit==null) {
				System.out.println("!!!@#^%$@##%*W%^&**");
				return;
			}
			_iCompilationUnit.delete(false, null);
			
			_isActive = true;
		} catch (JavaModelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public boolean isActive() {
		return _isActive;
	}

	@Override
	public String getDescription() {
		return toString();
	}

	@Override
	public Error getLastError() {
		return _lastError ;
	}
	
	private ICompilationUnit getIcompilationUnit() {
		IJavaElement iCompilationUnitOfSourceClass = _methods.get(0).resolveMethodBinding().getDeclaringClass().getJavaElement(); 
		while (iCompilationUnitOfSourceClass.getElementType() != IJavaElement.COMPILATION_UNIT)
			iCompilationUnitOfSourceClass = iCompilationUnitOfSourceClass.getParent();
		return (ICompilationUnit)iCompilationUnitOfSourceClass;
	}

	@Override
	public String getTargetModuleName() {
		return getIcompilationUnit().getElementName();
	}

	@Override
	public Map<String, String> getParameters() {
		Map<String, String> paramsMap = new HashMap<String, String>();
		paramsMap.put("interfaceName", _interfaceName);
		return paramsMap;
	}

	@Override
	public String getTaskName() {
		return _taskName;
	}

	@Override
	public IPath getPath() {
		return getIcompilationUnit().getPath();
	}

}
